use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Ident};

use super::{rcc_enable, RccAxB};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(uart_config))]
struct Attr {
    baud_rate: u32,
    uart: u8,
    size: u8,
}

struct Info {
    name: Ident,
    axb: RccAxB,
    field: Ident,
    freq: u32,
}

impl Attr {
    fn info(&self) -> Info {
        match self.uart {
            1 => Info {
                name: format_ident!("USART1"),
                axb: RccAxB::APB2,
                field: format_ident!("usart1en"),
                freq: 72000000,
            },
            2 => Info {
                name: format_ident!("USART2"),
                axb: RccAxB::APB1,
                field: format_ident!("usart2en"),
                freq: 36000000,
            },
            3 => Info {
                name: format_ident!("USART3"),
                axb: RccAxB::APB1,
                field: format_ident!("usart3en"),
                freq: 36000000,
            },
            4 => Info {
                name: format_ident!("UART4"),
                axb: RccAxB::APB1,
                field: format_ident!("uart4en"),
                freq: 36000000,
            },
            5 => Info {
                name: format_ident!("UART5"),
                axb: RccAxB::APB1,
                field: format_ident!("uart5en"),
                freq: 36000000,
            },
            _ => panic!("invalid uart"),
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let attr = Attr::from_derive_input(&input).unwrap();
    let info = attr.info();
    let rcc_src = rcc_enable(info.axb, &info.field, false);
    let uart_name = &info.name;
    let freq = info.freq;
    let m_val = format_ident!("m{}", attr.size);

    let uart_name_low = format_ident!("{}", info.name.to_string().to_lowercase());
    let init_fn = format_ident!("{}_init", info.name.to_string().to_lowercase());
    let is_idle_fn = format_ident!("{}_is_idle", info.name.to_string().to_lowercase());
    let clear_idle_fn = format_ident!("{}_clear_idle", info.name.to_string().to_lowercase());
    let baud_rate = attr.baud_rate;

    let expand = quote! {

        struct #uart_name;

        impl ::core::ops::Deref for #uart_name {
            type Target = stm32f1::stm32f103::#uart_name_low::RegisterBlock;
            fn deref(&self) -> &Self::Target {
                unsafe { &*stm32f1::stm32f103::#uart_name::PTR }
            }
        }

        impl #name {

            fn #is_idle_fn() -> bool {
                let uart: stm32f1::stm32f103::#uart_name = unsafe {
                    ::core::mem::transmute(())
                };
                uart.sr.read().idle().bit_is_set()
            }

            fn #clear_idle_fn() {
                let uart: stm32f1::stm32f103::#uart_name = unsafe {
                    ::core::mem::transmute(())
                };
                uart.dr.read();
            }

            fn #init_fn() {
                #rcc_src

                let uart: stm32f1::stm32f103::#uart_name = unsafe {
                    ::core::mem::transmute(())
                };
                uart.cr1.reset();
                uart.cr2.reset();
                uart.cr3.write(|w| {
                    w.dmat().enabled()
                    .dmar().enabled()
                });
                uart.brr.write(|w| unsafe {
                    w.bits(#freq / #baud_rate)
                });
                uart.cr1.write(|w| {
                    w.ue().enabled()
                    .m().#m_val()
                    .idleie().enabled()
                    .te().enabled()
                    .re().enabled()
                });
            }
        }
    };

    expand.into()
}
